XDL VideoView 활용 세 번째

NXVideoView를 활용하여 동영상 플레이어를 구현해 봅니다.
여기에서는 Timer를 통해 총 Frame수와 현재 도시되는 Frame정보를 획득하여 TrackBar를 통해 정보를 업데이트하여 도시하게 됩니다

들어가기 전에

설치 프로그램으로 배포되는 XDL 엔진은 Visual Studio 2022 x64 Release 버전으로, Visual Studio 2022 이상의 버전에서 사용 가능하다.
아래의 설명은 Visual Studio 2022를 기준으로 하겠다.

1 Play 시간 컨트롤 넣기
1.1 디자인창에서 label과 TrackBar를 이용해 다음과 같이 디자인한다. label_CurrentTime, trackBar_PlayControl, label_TotalTime을 입력한다. UI 디자인에 대해서는 C# 매뉴얼을 참조한다.

2 TrackBar 동기화
2.1 디자인창에서 Toolbar의 [File]-[Open]을 더블 클릭하여 함수를 자동 생성하고 두번째 매뉴얼을 기준으로 다음과 같이 기능을 추가한다. Timer를 추가하고 Frame정보를 위한 TotalFrame과 CurrentFrame을 설정한다.

C#

                                
enum VideoAction { STOP, PLAYING, PAUSED }

struct VideoState
{
    public XVideo video;                  // 파일이나 네트워크로부터 입력되는 스트리밍데이터를 제어하는 기능을 수행할 객체 선언
    public XVideoChannel videoChannel;    // 동영상 개체에 포함된 채널 객체 선언
    public string videoFilePath;          // 동영상 파일 경로 
    public VideoAction action;            // 비디오 플레이 상태를 정의하는 객체 선언
    public long currentFrame;              // 재생중인 동영상의 현재 프레임 위치
    public long totalFrame;                // 동영상 전체 프레임 수  
}

private XVideoIO m_videoIO = null;        // 동영상의 입출력을 담당할 객체 선언
private VideoState VS;                    // 비디오 상태를 관리하는 객체 선언

private System.Threading.Timer m_timer;         // 재생 컨트롤 바 동작을 위한 타이머
private object m_lockCurFrame = new object();   // 재생 컨트롤 바와의 동기화를 위한 Lock 객체
                                
                            

2.2 Timer를 생성하고 TotalFrame과 CurrentFrame를 초기화한다.

C#

                                
public Form1()
{
    InitializeComponent();
    m_videoIO = new XVideoIO(); // VideoIO를 생성
    
    VideoInit();
    nxVideoLayerOverlay1.LayerVisible = true;

    // 재생 컨트롤 바 동작을 위한 타이머 생성
    m_timer = new System.Threading.Timer(timer_Tick);

    // 타이머 옵션 변경
    m_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
}

private void VideoInit()
{
    // VideoState의 초기화
    VS.video = null;   
    VS.videoChannel = null;    
    VS.videoFilePath = string.Empty;
    VS.totalFrame = 0;
    VS.currentFrame = 0;
    VS.action = VideoAction.STOP;
}
    
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    // Play가 종료되었으므로 Timer를 종료한다.
    StopTimer();

    // Play를 중단한다.
    OnStop();

    if (m_videoIO != null)
    {
        // 동영상 입출력 객체 Dispose
        m_videoIO.Dispose();
    }
}
                                
                            

2.3 OnOpen 함수에 TotalFrame을 얻어 VideoState에 저장한다.

C#

                                
// 동영상 파일 열기
public void OnOpen()
{
    try
    {
        string strError = null;
        // 동영상 스트림 정보 가져오기
        VS.video = m_videoIO.OpenFile(VS.videoFilePath, "XFFMPDRIVER", out strError);

        if (VS.video == null)
        {
            MessageBox.Show(this, "동영상 재생에 실패하였습니다. 파일을 확인해주십시오.", "파일 열기");
            return;
        }

        // 다중 채널을 가진 동영상 객체의 Channel 인덱스
        int nIdxChannel = 0;

        // 동영상 뷰에 재생할 동영상 채널을 성정
        nxVideoView1.SetVideoChannel(VS.video, nIdxChannel);

        // 입력 인덱스에 해당하는 Channel을 가져오기
        VS.videoChannel = VS.video.GetChannel(nIdxChannel);

        // GetChannel에 실패할 경우 Null 객체가 return되며, 그에 대한 예외처리
        if (VS.videoChannel == null)
        {
            MessageBox.Show(this, "동영상 재생에 실패하였습니다. 파일을 확인해주십시오.", "파일 열기");
            return;
        }

        // 동영상 객체에 포함된 Channel 객체 중 해당 Channel 객체를 활성화
        // 활성화된 객체만 스트리밍이 수행
        VS.videoChannel.Activate();

        // 동영상의 Frame 정보를 얻어오기
        VS.totalFrame = VS.videoChannel.GetNumFramesVideo();

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
        MessageBox.Show(this, "재생 실패!", "동영상");
        VS.action = VideoAction.STOP;
    }
}
                                
                            

2.4 OnStop 함수에 에 currentFrame을 초기화한다.

C#

                                
// 동영상 정지버튼클릭 이벤트
public void OnStop()
{
    if (VS.videoChannel != null)
    {
        // 재생 Frame Buffer를 삭제
        VS.videoChannel.ClearFrameBuffer();
        // 동영상 재생 스크린을 갱신한다.
        nxVideoView1.RefreshScreen();
        // 동영상 재생 중지
        VS.videoChannel.Stop();
        VS.videoChannel = null;
    }

    // 동영상 채널 정보 초기화
    nxVideoView1.ResetVideoChannel();

    if (VS.video != null)
    {
        // 동영상 객체 Close
        VS.video.Close();
        VS.video = null;
    }

    VS.currentFrame = 0;
    VS.action = VideoAction.STOP;
}
                                
                            

2.5 [File]-[Open] 버튼을 눌렀을 때 기능을 구현한다.

C#

                                
private void toolStripMenuOpen_Click(object sender, EventArgs e)
{
    // 새로운 파일 Open을 수행한다.
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog.Filter = "TS file(*.ts)|*.ts||";
    openFileDialog.RestoreDirectory = true;

    if (openFileDialog.ShowDialog() != DialogResult.OK) return;

    string videoPath = openFileDialog.FileName;

    // 동영상 파일의 존재 유무 체크
    if (System.IO.File.Exists(videoPath) == false)
    {
        MessageBox.Show(this, "해당 경로에 영상이 없습니다.", "오류");
        return;
    }

    // 파일 경로를 저장한다.
    VS.videoFilePath = videoPath;

    // 둥영상의 재생상태가 중지가 아닐 경우 동영상 재생 중지
    OnStop();

    // 동영상 스트리밍 환경을 생성한다.=.
    OnOpen();

    // 동영상 상태를 업데이트하기 위해 Timer를 작동한다. 100msec간격으로 설정한다.
    OnTimer();

    // Play되는 시간 정보를 도시하기 위해 Timer설정과 TrackBar와 Label을 초기화 한다. 
    InitTrackBarLabel();

    // 동영상을 Play한다.
    OnPlay();
}
                                
                            

2.6 Play, Pause, Stop버튼을 눌렀을 때 기능을 구현한다.

C#

                                
private void button_Play_Click(object sender, EventArgs e)
{
    // 비디오가 Stop인 상태인 경우 다시 비디오를 저장된 파일 경로로부터 Open해서 설정한다. 
    if (VS.action == VideoAction.STOP)
    {
        // 동영상 상태를 업데이트하기 위해 Timer를 작동한다. 
        OnTimer();

        // 동영상 스트리밍 환경을 생성한다.
        OnOpen();
    }

    // 설정된 비디오를 Play한다.
    OnPlay();
}

private void button_Pause_Click(object sender, EventArgs e)
{
    // Play되고 있는 비디오를 Pause한다.
    OnPause();
}

private void button_Stop_Click(object sender, EventArgs e)
{
    // Play가 종료되었으므로 Timer를 종료한다.
    StopTimer();

    // Play를 중단한다.
    OnStop();

    // Play가 Stop되었으므로 TrackBar컨트롤과 Label을 초기화한다.
    InitTrackBarLabel();
}
                                
                            

2.7 InitTrackBarLabel, OnTimer, StopTimer를 정의한다.

C#

                                
public void InitTrackBarLabel()
{
    // TrackBar를 설정하기 위해 컨트롤의 최대값을 설정한다. 
    trackBar_PlayControl.Maximum = unchecked((int)VS.totalFrame);

    // Total 시간에 대해 정보를 Label로 설정한다. 
    int nTotalSec = unchecked((int)(VS.totalFrame * (1.0 / 30)));
    TimeSpan getTotalTimeSpan = TimeSpan.FromSeconds(nTotalSec);
    string strTime = getTotalTimeSpan.ToString("hh':'mm':'ss");
    label_TotalTime.Text = strTime;
}

public void OnTimer()
{
    m_timer.Change(0, 100);
}

public void StopTimer()
{
    if (m_timer != null)
    {
        m_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
    }
}
                                
                            

2.8 디자인창에서 Video Overlay Layer컨트롤을 클릭하고 [속성]-[이벤트]탭에서 OnOrthoRender행을 더블 클릭하여 nxVideoLayerOverlay1_OnOrthoRender함수를 자동 생성하고 NXVideoDrawArgs 인자로 넘어오는 값으로부터 PTS값을 얻어 현재 Frame이 몇 번째인지를 계산하여 currentFrame에 저장한다.
참고로 OnOrthoRender는 화면이 갱신될 때 마다 호출되는 Callback함수이다.

C#

                                
// PTS값을 얻기 위해 OnOrthoRender함수를 이용한다.
private bool nxVideoLayerOverlay1_OnOrthoRender(NXVideoLayer sender, NXVideoDrawArgs DrawArgs)
{
    try
    {
        if (VS.videoChannel == null) return false;
        lock (m_lockCurFrame)
        {
            // 현재 재생중인 화면의 PTS(Presentation TimeStamp) 값 얻어오기
            Int64 pts = DrawArgs.PTS;

            // PTS를 이용한 현재 재생중인 화면의 프레임 위치를 얻어오기
            VS.currentFrame = VS.videoChannel.PtsToFrameNumber(pts);
            if (VS.currentFrame >= VS.totalFrame) VS.currentFrame = VS.totalFrame;
            if (VS.currentFrame < 0) VS.currentFrame = 0;
        }
    }
    catch (System.Exception ex)
    {
        Console.WriteLine(ex);	
    }
    return default(bool);
}
                                
                            

2.9 Timer함수를 통해 현재 도시되는 Frame이 어느 정도 되는지와 각종 재생 상태를 갱신하고 만약 Play가 종료시점에 도달하면 Stop을 수행한다

C#

                                
// 재생 상태 처리를 위한 타이머 함수
private void timer_Tick(object state)
{
    lock (m_lockCurFrame)
    {
        if (VS.action != VideoAction.STOP)
        {
            // 현재 재생되고 있는 동영상의 시간을 계산
            int nSec = unchecked((int)(VS.currentFrame * (1.0 / 30)));
            int nTotalSec = unchecked((int)(VS.totalFrame * (1.0 / 30)));

            long nCheckFrame = VS.totalFrame - VS.currentFrame;

            if (nCheckFrame <= 30)
            {
                nSec = nTotalSec;
            }

            string strTime;

            TimeSpan getTimeSpan = TimeSpan.FromSeconds(nSec);
            strTime = getTimeSpan.ToString("hh':'mm':'ss");
            int nCurFrame = unchecked((int)VS.currentFrame);

            BeginInvoke(new Action(delegate
            {
                // 현재 재생되고 있는 동영상의 시간을 문자형태로 화면 도시
                label_CurrentTime.Text = strTime;

                // 현재 재생되고 있는 동영상의 시간을 Track 바 컨트롤에 도시
                trackBar_PlayControl.Value = nCurFrame;
                if (nSec == nTotalSec)
                {
                    // Play가 종료되었으므로 Timer를 종료한다.
                    StopTimer();

                    // Play를 중단한다.
                    OnStop();

                    // Play가 Stop되었으므로 TrackBar컨트롤과 Label을 초기화한다.
                    InitTrackBarLabel();
                }
            }));
        }
    }
}
                                
                            

2.10 [F5]키를 눌러 프로그램을 실행하여 stream.ts파일을 Open한다.

1 Play 시간 컨트롤 넣기
1.1 디자인창에서 label과 Slider를 이용해 다음 아래와 그림 같이 디자인한다. 표를 참고하여 “currentTimeLabel”을 (1)에 그리고 “playControlSlider”을 (2)에, “totalTimeLabel”을 (3)에 입력한다. (자세한 UI 디자인에 대해서는 샘플코드를 참조한다.)
Number Control Type Name
(1)) Label currentTimeLabel
(2) Slider playControlSlider
(3) Label totalTimeLabl

2 Slider 동기화
2.1 Window창에서 Menu의 [File]-[Open]을 더블 클릭하여 함수를 자동 생성하고 두 번째 매뉴얼을 기준으로 다음과 같이 기능을 추가한다. Timer를 추가하고 Frame정보를 위한 TotalFrame과 CurrentFrame을 설정한다.

C#

                                
public partial class MainWindow : Window
{
    enum VideoAction { STOP, PLAYING, PAUSED }
    struct VideoState
    {
        public XVideo video;                // 파일이나 네트워크로부터 입력되는 스트리밍데이터를 제어하는 기능을 수행할 객체 선언
        public XVideoChannel videoChannel;  // 동영상 개체에 포함된 채널 객체 선언
        public string videoFilePath;        // 동영상 파일 경로
        public VideoAction action;          // 비디오 플레이 상태를 정의하는 객체 선언
        public long currentFrame;           // 재생중인 동영상의 현재 프레임 위치
        public long totalFrame;             // 동영상 전체 프레임 수
    }
    private XVideoIO m_videoIO = null;
    private VideoState VS; // 비디오 상태를 관리하는 객체 선언

    private System.Threading.Timer m_timer;         // 재생 컨트롤 바 동작을 위한 타이머
    private object m_lockCurFrame = new object();   // 재생 컨트롤 바와의 동기화를 위한 Lock 객체
                                
                            

2.2 Timer를 생성하고 TotalFrame과 CurrentFrame를 초기화한다.

C#

                                
public MainWindow()
{
    InitializeComponent();
    m_videoIO = new XVideoIO();  // VideoIO를 생성

    VideoInit();
    nxVideoLayerOverlay1.LayerVisible = true;

    // 재생 컨트롤 바 동작을 위한 타이머 생성
    m_timer = new System.Threading.Timer(timer_Tick);

    // 타이머 옵션 변경
    m_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
}

private void VideoInit()
{
    // VideoState의 초기화
    VS.video = null;
    VS.videoChannel = null;
    VS.videoFilePath = string.Empty;
    VS.action = VideoAction.STOP;
    VS.totalFrame = 0;
    VS.currentFrame = 0;
}
                                
                            

2.3 OnOpen 함수에 TotalFrame을 얻어 VideoState에 저장한다.

C#

                                
// 동영상 파일 열기
public void OnOpen()
{
    try
    {
        string strError = null;
        // 동영상 스트림 정보 가져오기
        VS.video = m_videoIO.OpenFile(VS.videoFilePath, "XFFMPDRIVER", out strError);

        if (VS.video == null)
        {
            MessageBox.Show(this, "동영상 재생에 실패하셨습니다. 파일을 확인해주십시오.", "파일 열기");
            return;
        }

        // 다중 채널을 가진 동영상 객체의 Channel 인덱스
        int nIdxChannel = 0;

        // 동영상 뷰에 재생할 동영상 채널의 설정
        nxVideoView1.SetVideoChannel(VS.video, nIdxChannel);

        // 입력 인덱스에 해당하는 Channel 가져오기
        VS.videoChannel = VS.video.GetChannel(nIdxChannel);

        // GetChannel에 실패할 경우 Null 객체가 return되며, 그에 대한 예외처리
        if (VS.videoChannel == null)
        {
            MessageBox.Show(this, "동영상 재생에 실패하였습니다. 파일을 확인해 주십시오.", "파일 열기");
            return;
        }

        // 동영상 객체에 포함된 Channel 객체 중 해당 Channel 객체를 활성화
        //활성화된 객체만 스트리밍이 수행
        VS.videoChannel.Activate();

        // 동영상의 Frame 정보를 얻어오기
        VS.totalFrame = VS.videoChannel.GetNumFramesVideo();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
        MessageBox.Show(this, "재생 실패!", "동영상");
        VS.action = VideoAction.STOP;
    }
}
                                
                            

2.4 OnStop 함수에 에 currentFrame을 초기화한다.

C#

                                
public void OnStop()
{
    if (VS.videoChannel != null)
    {
        // 재생 Frame Buffer를 삭제
        VS.videoChannel.ClearFrameBuffer();
        // 동영상 재생 스크린을 갱신한다.
        nxVideoView1.RefreshScreen();
        // 동영상 재생 중지
        VS.videoChannel.Stop();
        VS.videoChannel = null;
    }

    // 동영상 채널 정보 초기화
    nxVideoView1.ResetVideoChannel();

    if (VS.video != null)
    {
        // 동영상 객체 Close
        VS.video.Close();
        VS.video = null;
    }

    VS.currentFrame = 0;
    VS.action = VideoAction.STOP;
}
                                
                            

2.5 [File]-[Open] 버튼을 눌렀을 때 기능을 구현한다.

C#

                                
private void openFileMenuItem_Click(object sender, RoutedEventArgs e)
{
    // 새로운 파일 Open을 수행한다.
    OpenFileDialog openFileDialog = new OpenFileDialog();
    openFileDialog.Filter = "TS file(*ts)|*.ts||";
    openFileDialog.RestoreDirectory = true;

    Nullable<bool> result = openFileDialog.ShowDialog();
    if (result != true) return;

    string videoPath = openFileDialog.FileName;

    // 동영상 파일의 존재 유무 체크
    if (System.IO.File.Exists(videoPath) == false)
    {
        MessageBox.Show(this, "해당 경로에 영상이 없습니다.", "오류");
        return;
    }

    // 파일 경로를 저장한다.
    VS.videoFilePath = videoPath;

    // 둥영상의 재생상태가 중지가 아닐 경우 동영상 재생 중지
    OnStop();

    // 동영상 상태를 업데이트하기 위해 Timer를 작동한다. 100msec간격으로 설정한다.
    OnTimer();

    // 동영상 스트리밍 환경을 생성한다.
    OnOpen();

    // Play되는 시간 정보를 도시하기 위해 Timer설정과 Slider와 Label을 초기화 한다. 
    InitSliderLabel();

    // 동영상을 Play한다.
    OnPlay();
}
                                
                            

2.6 [▶], [∥], [■] 버튼을 눌렀을 때 기능을 구현한다.

C#

                                
private void playButton_Click(object sender, RoutedEventArgs e)
{
    // 비디오가 Stop인 상태인 경우 다시 비디오를 저장된 파일 경로로부터 Open해서 설정한다. 
    if (VS.action == VideoAction.STOP)
    {
        // 동영상 상태를 업데이트하기 위해 Timer를 작동한다. 
        OnTimer();

        // 동영상 스트리밍 환경을 생성한다.
        OnOpen();
    }

    // 설정된 비디오를 Play한다.
    OnPlay();
}

private void pauseButton_Click(object sender, RoutedEventArgs e)
{
    // Play되고 있는 비디오를 Pause한다.
    OnPause();
}

private void stopButton_Click(object sender, RoutedEventArgs e)
{
    // Play가 종료되었으므로 Timer를 종료한다.
    StopTimer();

    // Play를 중단한다.
    OnStop();

    // Play가 Stop되었으므로 Slider컨트롤과 Label을 초기화한다.
    InitSliderLabel();
}
                                
                            

2.7 InitSliderLabel, OnTimer, StopTimer를 정의한다.

C#

                                
private void InitSliderLabel()
{
    // Slider를 설정하기 위해 컨트롤의 최대값을 설정한다. 
    playControlSlider.Maximum = unchecked((int)VS.totalFrame);

    // Total 시간에 대해 정보를 Label로 설정한다. 
    int nTotalSec = unchecked((int)(VS.totalFrame * (1.0 / 30)));
    TimeSpan getTotalTimeSpan = TimeSpan.FromSeconds(nTotalSec);
    string strTime = getTotalTimeSpan.ToString("hh':'mm':'ss");
    totalTimeLabel.Content = strTime;
}

public void OnTimer()
{
    m_timer.Change(0, 100);
}

public void StopTimer()
{
    if (m_timer != null)
    {
        m_timer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
    }
}
                                
                            

2.8 Window 창에서 Video Overlay Layer컨트롤을 클릭하고 [속성]-[이벤트 ] 탭에서 OnOrthoRender행을 더블 클릭하여 nxVideoLayerOverlay1_OnOrthoRender함수를 자동 생성한다.
NXVideoDrawArgs 인자로 넘어오는 값으로부터 PTS값을 얻어 현재 Frame이 몇 번째인지를 계산하여 currentFrame에 저장하도록 한다.
참고로 OnOrthoRender는 화면이 갱신될 때 마다 호출되는 Callback함수이다.

C#

                                
// PTS 값을 얻기 위해 OnOrthoRender 함수를 이용한다.
private bool nxVideoLayerOverlay1_OnOrthoRender(NXVideoLayer sender, NXVideoDrawArgs DrawArgs)
{
    try
    {
        if (VS.videoChannel == null) return false;
        lock (m_lockCurFrame)
        {
            // 현재 재생중인 화면이 PTS(Presentation TimeStamp) 값 얻어오기
            Int64 pts = DrawArgs.PTS;

            // PTS를 이용한 현재 재생중인 화면의 프레임 위치를 얻어오기
            VS.currentFrame = VS.videoChannel.PtsToFrameNumber(pts);
            if (VS.currentFrame >= VS.totalFrame) VS.currentFrame = VS.totalFrame;
            if (VS.currentFrame < 0) VS.currentFrame = 0;
        }
    }
    catch (System.Exception ex)
    {
        Console.WriteLine(ex);
    }
    return default(bool);
}
                                
                            

2.9 Timer함수를 통해 현재 도시되는 Frame이 어느 정도 되는지와 각종 재생 상태를 갱신하고 만약 Play가 종료시점에 도달하면 Stop을 수행한다.

C#

                                
private void timer_Tick(object state)
{
    lock (m_lockCurFrame)
    {
        if (VS.action != VideoAction.STOP)
        {
            // 현재 재생되고 있는 동영상의 시간을 계산
            int nSec = unchecked((int)(VS.currentFrame * (1.0 / 30)));
            int nTotalSec = unchecked((int)(VS.totalFrame * (1.0 / 30)));

            long nCheckFrame = VS.totalFrame - VS.currentFrame;

            if (nCheckFrame <= 30)
            {
                nSec = nTotalSec;
            }

            string strTime;

            TimeSpan getTimeSpan = TimeSpan.FromSeconds(nSec);
            strTime = getTimeSpan.ToString("hh':'mm':'ss");
            int nCurFrame = unchecked((int)VS.currentFrame);

            Dispatcher.BeginInvoke(new Action(delegate
            {
                // 현재 재생되고 있는 동영상의 시간을 문자형태로 화면 도시
                currentTimeLabel.Content = strTime;

                // 현재 재생되고 있는 동영상의 시간을 Track 바 컨트롤에 도시
                playControlSlider.Value = nCurFrame;
                if (nSec == nTotalSec)
                {
                    playControlSlider.Value = playControlSlider.Maximum;

                    // Play가 종료되었으므로 Timer를 종료한다.
                    StopTimer();

                    // Play를 중단한다.
                    OnStop();

                    // Play가 Stop되었으므로 Slider컨트롤과 Label을 초기화한다.
                    InitSliderLabel();
                }
            }));
        }
    }
}                                
                            

2.10 [F5]키를 눌러 프로그램을 실행하여 홈페이지에서 다운 받은 stream.ts파일을 Open한다.